#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


typedef struct {
    int pid;
    struct {
        int private_clean;
        int private_dirty;
        int uss;
        int pss;
        int rss;
        int swap;
    } map;
    char user[32];
    char cmd[32];
    char cmdline[32];
} proc_t;



int load(const char *name, char *buf, int len)
{
    int fd, ret;

    fd = open(name, O_RDONLY);
    if (fd < 0)
        return -1;

    ret = read(fd, buf, len);
    if (ret > 0)
        buf[ret] = '\0';

    close(fd);

    return ret;
}

char *get_column(char *line, int index)
{
    char *token;

    while ((token = strsep(&line, " ")) != NULL) {
        if (*token)
            if (--index < 0)
                return token;
    }
    return NULL;
}

#define starts_with(str, fix)    !(strncmp(str, fix, sizeof(fix) - 1))

int get_proc_user(proc_t *proc, char *pid)
{
    char file[128];

    sprintf(file, "/proc/%s", pid);

    struct stat filestat;
    if (stat(file, &filestat) < 0) {
        return -1;
    }

    struct passwd *pw = getpwuid(filestat.st_uid);

    int len = strlen(pw->pw_name);
    if (len > sizeof(proc->user) - 1)
        len = sizeof(proc->user) - 1;
    strncpy(proc->user, pw->pw_name, len);
    proc->user[len] = '\0';

    return 0;
}

int get_proc_cmd(proc_t *proc, char *pid)
{
    char file[128];

    sprintf(file, "/proc/%s/comm", pid);

    int ret = load(file, proc->cmd, sizeof(proc->cmd) - 1);
    if (ret <= 0)
        return -1;
    if (ret > 1 && proc->cmd[ret - 1] == '\n')
        proc->cmd[ret - 1] = '\0';

    return 0;
}

int get_proc_cmdline(proc_t *proc, char *pid)
{
    char file[128];

    sprintf(file, "/proc/%s/cmdline", pid);

    int ret = load(file, proc->cmdline, sizeof(proc->cmdline) - 1);
    if (ret <= 0)
        return -1;
    if (ret > 1 && proc->cmdline[ret - 1] == '\n')
        proc->cmdline[ret - 1] = '\0';
    for (int i = 0; i < ret; i++)
        if (proc->cmdline[i] == '\0')
            proc->cmdline[i] = ' ';

    return 0;
}

int get_proc_map(proc_t *proc, char *pid)
{
    char buf[4096];
    char file[128];

    sprintf(file, "/proc/%s/smaps_rollup", pid);

    int ret = load(file, buf, sizeof(buf) - 1);
    if (ret <= 0)
        return -1;

    char *sep = buf;
    char *token;
    while ((token = strsep(&sep, "\n")) != NULL) {
        int *value = NULL;
        if (starts_with(token, "Rss:"))
            value = &proc->map.rss;
        else if (starts_with(token, "Pss:"))
            value = &proc->map.pss;
        else if (starts_with(token, "Swap:"))
            value = &proc->map.swap;
        else if (starts_with(token, "Private_Clean:"))
            value = &proc->map.private_clean;
        else if (starts_with(token, "Private_Dirty:"))
            value = &proc->map.private_dirty;

        if (value != NULL) {
            char *column = get_column(token, 1);
            if (column != NULL) {
                *value = atoi(column);
                //printf("pid=%s, %s, %d\n", pid, column, *value);
            }
        }
    }
    proc->map.uss = proc->map.private_clean + proc->map.private_dirty;

    return 0;
}

int is_digit(const char *str)
{
    while (*str) {
        if (*str < '0' || *str > '9')
            return 0;
        str++;
    }
    return 1;
}

int main(int argc, char *argv[])
{
    DIR *dir;
    struct dirent *entry;

    dir = opendir("/proc");
    if (dir == NULL) {
        printf("can't open: /proc\n");
        return -1;
    }

    proc_t proc;
    printf("%6s %8s %8s %8s %8s  %-8s %-32s\n", "PID", "SWAP", "USS", "PSS", "RSS", "USER", "CMD");
    // printf("--------------------------------------------------\n");
    int ret;
    char *pid;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_DIR && is_digit(entry->d_name)) {
            pid = entry->d_name;
            proc.pid = atoi(pid);
            get_proc_user(&proc, pid);
            get_proc_cmd(&proc, pid);
            get_proc_cmdline(&proc, pid);
            ret = get_proc_map(&proc, pid);
            if (ret == 0)
                printf("%6d %8d %8d %8d %8d  %-8s %-32s\n", \
                        proc.pid, proc.map.swap, proc.map.uss, proc.map.pss, proc.map.rss, proc.user, proc.cmdline);
        }
    }

    closedir(dir);

    return 0;
}
